/** \file    avi.h
  * \brief   AVI-streams facilities.
  * \date    2003
  * \author  Fedor Chelnokov
  * \version $Header: /home/cscom1r4/cvsroot/cs/include/imglib/avi.h,v 1.14 2005/09/17 16:35:01 cscom1r4 Exp $
  */

#ifndef _avi_h_
#define _avi_h_

#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#include "vfw.h"
#include <string>
#include <vector>
#include "imglib/imageclass.h"
#include "typelib/types.h"
#include "typelib/fraction.h"
#include <xtd/exception>

namespace img {

namespace avi {

//ensures that AVIFile library is initialized during lifetime of an object
class user
{
public:
	user();
	~user();
};

struct exception : xtd::exception {
	exception() throw()	{ }
	exception(const std::string & msg) throw() : xtd::exception(msg) { }
};

const char * decode_icerr(DWORD e);

struct format
{
	format() {}
	format(size_extent d, fraction frame_per_sec, bool a = false) :
		dim(d),
		fps(frame_per_sec),
		alpha(a)
	{ }

	void fill(BITMAPINFO & info) const;

	size_extent dim;
	fraction fps;
	bool alpha;
};

union codec_id {
	DWORD fccHandler;
	char code[5];
	codec_id() { code[4] = 0; }
	codec_id & operator = (std::string const & name);
};

struct coder_info
{
	coder_info() {}
	void interrogate(HIC hic, BITMAPINFOHEADER * in_format);

	codec_id id;
	std::string name, description;
	bool alpha, noalpha; ///< support for 24 and 32 bit modes
	bool quality;
	bool temporal;	///< allows not key frames
	bool crunch;	///< supports data rate settings
	bool has_about_dialog;
	bool configurable; ///< coder has its own configuation dialog
};

typedef std::vector<coder_info> coder_infos;

void enum_coders(coder_infos & a, size_extent e = size_extent(720, 576));

typedef std::vector<char> state;

class coder
{
public:
	coder();
	coder(const coder_info & ci);
	coder(const coder & rhs);
	~coder();
	coder & operator = (const coder & rhs); ///< both coders share one coder_info

	///ci should be valid structure during lifetime of the coder
	void open(const coder_info & ci);
	///opens coder by name (coder_info is filled during the call, it must be valid as noted above)
	void open(coder_info & ci, DWORD fccHandler, BITMAPINFOHEADER * in_format = 0);
	///allows the user to select a coder to open (coder_info is filled during the call, it must be valid as noted above),
	///returns true if the user chooses a compressor and presses Ok. Returns false on error or if the user presses cancel.
	bool choose(coder_info & ci, HWND hwnd, BITMAPINFOHEADER * in_format = 0, PAVISTREAM preview = 0);
	void close();

	bool is_open() const
		{ return _pci != 0; }

	const coder_info * info() const
		{ return _pci; }
	HIC hic() const
		{ return _hic; }

	void set_state(const avi::state & s);
	void state(avi::state & s) const;

	///we can't know whether the compressor's state might have been changed 
	///(user pressed OK or Cancel in dialog), because some coders return same code in either case
	///\param hwnd should not be zero because some coders will not show their dialogs in such a case
	void configure(HWND hwnd);
	void about(HWND hwnd);

private:
	const coder_info * _pci;
	HIC _hic;
};

class writer
{
public:
	writer(coder & c, const std::string & filename, const format & f);
	writer(coder & c);
	~writer();
	void open(const std::string & filename, const format & f);
	void close();

	bool is_open() const
		{ return _ps && _pfile && _lpbiOut && _outBuffer; }
	HIC hic() const
		{ return _coder.hic(); }

	writer & operator << (const byte_image & img);
	writer & operator << (const small_byte_image & img);

private:
	void init();
	void write_frame(const void * data, const void * prev_data = 0);

private:
	user _u;
	coder & _coder;
	format _fmt;
	unsigned int _scansize;
	int _frames_out;
	PAVIFILE _pfile;
	PAVISTREAM _ps;
	BITMAPINFO _in;
	LPBITMAPINFOHEADER _lpbiOut;
	char * _outBuffer;

	//storages for previous frame
	byte_image _prev32;
	small_byte_image _prev24;
};

//
// Read from AVI
//

///represents videosequence stored in AVI
class footage : private user
{
public:
	footage();
	footage(const std::string & filename);
	~footage();
	void open(const std::string & filename);
	void close();
public:
	PAVIFILE hfile() const
		{ return _file; }
	PAVISTREAM hstream() const
		{ return _stream; }
public:
	bool is_open() const
		{ return _file && _stream; }
	int duration() const
		{ return _duration; }
	bool all_keyframes() const;
private:
	PAVIFILE _file;
	PAVISTREAM _stream;
	int _duration;
};

class reader
{
public:
	reader(const footage & f, int frame);
	~reader();
	///if another key frame is nearer to given frame, restarts reading from that key frame
	void jump(int frame);
public:
	const footage & get_footage() const
		{ return _footage; }
	PAVIFILE hfile() const
		{ return _footage.hfile(); }
	PAVISTREAM hstream() const
		{ return _footage.hstream(); }
	HIC hic() const
		{ return _hic; }
	fraction fps() const
		{ return _fps; }
	int nearest_keyframe_to(int frame) const;
	///return true if driver may return in 32bit format, otherwise to receive 32bit image additional copying is required
	bool native_32bit() const
		{ return out_format()->biBitCount == 32; }
public:
	///position of base key frame for the next frame
	int base_key() const
		{ return _base_key; }
	///position of last read frame
	int last() const
		{ return _next-1; }
	///position of the frame that will be read next
	int next() const
		{ return _next; }
	///position of nearest key frame after the next frame (or -1 if there is no more key frames in stream)
	int next_key() const
		{ return _next_key; }
	///is any frames available in the stream?
	bool eof() const
		{ return _next == _footage.duration(); }
public:
	//in the case of error and if nothrow is set, replaces result of decoding with black picture
	void read_next(bool nothrow = false);
	byte_image const & frame();
	small_byte_image const & frame24();
	size_extent dim() const
		{ return _dim; }
public:
	LPBITMAPINFOHEADER in_format()
		{ return (LPBITMAPINFOHEADER) &_in_format[0]; }
	LPBITMAPINFOHEADER out_format()
		{ return (LPBITMAPINFOHEADER) &_out_format[0]; }
	const LPBITMAPINFOHEADER out_format() const
		{ return (const LPBITMAPINFOHEADER) &_out_format[0]; }
private:
	const footage & _footage;
	HIC _hic;
	int _base_key, _next, _next_key; ///< frame numbers: _base_key <= _next < _next_key or _next_key = -1 (if there is no more key frames in stream)
	size_extent _dim;
	small_byte_image _im24;
	byte_image _im32;
	bool _valid24, _valid32;
	std::vector<char> _in_format, _out_format;
	fraction _fps;
};

} //namespace avi

} //namespace img

#endif //_avi_h_
